# -*- coding: utf-8 -*-
# @Time    : 2024/6/19 18:01
# @Author  : yujiahao
# @File    : 33_serialize.py
# @description:Python 中的序列化

'''
序列化模块
序列化模块就是将一个常见的数据结构（如字典、列表等）转化成一个特殊的序列（字符串或者bytes），并且这个特殊的序列还可以反解回去。
序列化的主要用途：文件读写数据，网络传输数据。

Python中常见的序列化模块有三种：
    1. json模块: 不同语言都遵循的一种数据转化格式，适用于跨语言的数据交换（重点）。

        ① 不同语言都遵循的一种数据转化格式，即不同语言都使用的特殊字符串。（比如Python的一个列表[1, 2, 3]利用json转化成特殊的字符串，
            然后在编码成bytes发送给php的开发者，php的开发者就可以解码成特殊的字符串，然后在反解成原数组(列表): [1, 2, 3]）

        ② json序列化只支持部分Python数据结构：dict,list, tuple,str,int, float,True,False,None

    2. pickle模块: 只能在Python语言中使用，支持Python所有的数据类型。

        ① 只能是Python语言遵循的一种数据转化格式，只能在python语言中使用。

        ② 支持Python所有的数据类型包括实例化对象。

    3. shelve模块: 类似于字典的操作方式去操作特殊的字符串（了解）。

'''

# todo 一、JSON

# todo 1.1 json模块

'''
json模块是将满足条件的数据结构转化成特殊的字符串，并且也可以反序列化还原回去。

1、序列化模块总共只有两种用法，要不就是用于网络传输的中间环节，要不就是文件存储的中间环节，所以json模块总共就有两对四个方法：

    用于网络传输：dumps、loads
    
    用于文件写读：dump、load
    
    
2、Python 数据类型与 JSON 数据类型的映射关系

    ① python 原始类型向 json 类型的转化对照表：
    
        +-------------------+---------+
        | Python            | JSON    |
        +-------------------+---------+
        | dict              | object  |
        | list, tuple       | array   |
        | str, unicode      | string  |
        | int, long, float  | number  |
        | True              | true    |
        | False             | false   |
        | None              | null    |
        +-------------------+---------+


    ② JSON 类型转换到 Python 类型的对照表：
    
        +----------------+-------------------+
        | JSON           | Python            |
        +----------------+-------------------+
        | object         | dict              |
        | array          | list              |
        | string         | str               |
        | number (int)   | int, long         |
        | number (real)  | float             |
        | true           | True              |
        | false          | False             |
        | null           | None              |
        +----------------+-------------------+

 3、json 中常用的方法
 


    +--------------+-------------------------------------------------------+----------------------------------------------+--------------------------------------------------------------------+
    |    方法      |                         描述                           |                   基本语法                     |                           语法名词解释                             |
    +--------------+-------------------------------------------------------+----------------------------------------------+--------------------------------------------------------------------+
    |              | 将 Python 对象编码成 JSON 字符串                         | json.dumps( obj,                             | obj: 要编码的 Python 对象                                           |
    | json.dumps() |                                                       |             indent=4,                        | indent: 可选参数，用于指定缩进层级，默认值为 None                   |
    |              |                                                       |             sort_keys=True,                  | sort_keys: 可选参数，指定是否按键排序，默认值为 False               |
    |              |                                                       |             allow_nan=True  )                | allow_nan: 可选参数，指定是否允许 NaN、Infinity 和 -Infinity，默认值为 True |
    +--------------+-------------------------------------------------------+----------------------------------------------+--------------------------------------------------------------------+
    | json.loads() | 将已编码的 JSON 字符串解码为 Python 对象                  | json.loads(json_string)                      | json_string: 要解码的 JSON 字符串                                  |
    +--------------+-------------------------------------------------------+----------------------------------------------+--------------------------------------------------------------------+
    |              |                                                       | json.dump(   obj,                            | obj: 要序列化的 Python 对象                                        |
    |              |                                                       |              fp,                             | fp: 文件对象，用于写入 JSON 数据                                    |
    | json.dump()  |       将 Python 内置类型序列化为 JSON 对象后写入文件       |              indent=4,                       | indent: 可选参数，用于指定缩进层级，默认值为 None                   |
    |              |                                                       |              sort_keys=True,                 | sort_keys: 可选参数，指定是否按键排序，默认值为 False               |
    |              |                                                       |              allow_nan=True)                 | allow_nan: 可选参数，指定是否允许 NaN、Infinity 和 -Infinity，默认值为 True |
    +--------------+-------------------------------------------------------+----------------------------------------------+--------------------------------------------------------------------+
    | json.load()  | 读取文件中 JSON 形式的字符串元素转化为 Python 类型          | json.load(fp)                                | fp: 文件对象，包含 JSON 格式的数据                                  |
    +--------------+-------------------------------------------------------+----------------------------------------------+--------------------------------------------------------------------+
    
    

 '''

# todo 1.1、网络传输

'''注意，json转换完的字符串类型的字典中的字符串是由""表示的'''

import json

dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}

# ① 序列化：将一个字典转换成一个字符串
str_dic = json.dumps(dic)
print(type(str_dic), str_dic)

# ② 反序列化 将字符串类型的字典转换成字典类型
print(json.loads(str_dic))

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# ③ 列表类型互转
list_dic = [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
str_lst = json.dumps(list_dic)
print(type(str_lst), str_lst)
list_dic2 = json.loads(str_lst)
print(type(list_dic2), list_dic2)

# todo 1.2、文件读写
# 设置相对路径
input_path = '../../../input/jsonload.json'
output_path = '../../../output/jsondump.json'
jsonlines_path = '../../../output/jsonlines.jsonl'
bigjson_path = '../../../input/bigjson.json'

# ① 将对象转换成字符串写入到文件当中

with open(output_path, 'w', encoding='utf-8') as f:
    dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
    # dump方法接收一个文件句柄，直接将字典转换成json字符串写入文件
    json.dump(dic, f)

# ② 将文件中的字符串类型的字典转换成字典
with open(input_path, 'r', encoding='utf-8') as f:
    # load方法接收一个文件句柄，直接将文件中的json字符串转换成数据结构返回
    dic2 = json.load(f)
    print(type(dic2), dic2)

# todo 1.3、 json序列化存储多个数据到同一个文件中

'''对于json序列化，存储多个数据到一个文件中是有问题的，默认一个json文件只能存储一个json数据，但是也可以解决'''

dic1 = {'name': 'oldboy1'}
dic2 = {'name': 'oldboy2'}
dic3 = {'name': 'oldboy3'}

f = open(jsonlines_path, encoding='utf-8', mode='a')
str1 = json.dumps(dic1)
f.write(str1 + '\n')
str2 = json.dumps(dic2)
f.write(str2 + '\n')
str3 = json.dumps(dic3)
f.write(str3 + '\n')
f.close()

with open(output_path, mode='r', encoding='utf-8') as f:
    for line in f:
        print(json.loads(line))

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 2、jsonl

'''
【JSONL 格式简介】

    JSONL（Jsonlines 或 newline-delimited JSON）是一种基于 JSON 的文件格式，其中每一行都是一个独立的 JSON 对象。
它的主要特点是每一行都是一个完整的 JSON 对象，并且行与行之间没有分隔符（除了换行符）。这种格式非常适合用于日志记录、数据流处理和大规模数据存储与传输。

【JSONL 的优点】
    
    - 简单易读：每一行都是一个独立的 JSON 对象，便于阅读和调试。
    
    - 流处理友好：可以逐行读取和处理数据，适合大数据量的流式处理。
    
    - 增量处理：可以方便地追加数据，不需要重新解析整个文件。
    
    - 兼容性好：JSON 是一种通用的数据交换格式，JSONL 继承了 JSON 的优点。

【JSONL 文件示例】

一个典型的 JSONL 文件可能如下所示：每一行都是一个独立的 JSON 对象，行与行之间通过换行符分隔，文件名.jsonl 。

    {"name": "Alice", "age": 30, "city": "New York"}
    {"name": "Bob", "age": 25, "city": "Los Angeles"}
    {"name": "Charlie", "age": 35, "city": "Chicago"}




'''

# todo 2.1、 通过Json模块操作

# ① 写入jsonl文件

data = [
    {"name": "Alice", "age": 30, "city": "New York"},
    {"name": "Bob", "age": 25, "city": "Los Angeles"},
    {"name": "Charlie", "age": 35, "city": "Chicago"}
]

with open(jsonlines_path, 'w', encoding='utf-8') as file:
    for item in data:
        json_str = json.dumps(item)
        file.write(json_str + '\n')

# ② 读取 JSONL 文件

'''可以使用 Python 的 json 模块逐行读取 JSONL '''

# 新建列表用来存每一行的JSON数据
json_objects = []
with open(jsonlines_path, 'r', encoding='utf-8') as file:
    for line in file:
        # 因为是按照行存储一个完整的JSON,那么就直接按行切割然后追加到列表中，打印列表就行
        json_object = json.loads(line.strip())
        json_objects.append(json_object)
        print(json_object)

# todo 2.2、 使用 jsonlines 库

import jsonlines

# ① 使用 jsonlines 库写入 JSONL 文件：

data = [
    {"name": "Alice", "age": 30, "city": "New York"},
    {"name": "Bob", "age": 25, "city": "Los Angeles"},
    {"name": "Charlie", "age": 35, "city": "Chicago"}
]

with jsonlines.open(output_path, mode='w') as writer:
    for item in data:
        writer.write(item)

# ② 使用 jsonlines 库读取 JSONL 文件

json_objects = []
with jsonlines.open(jsonlines_path) as reader:
    for obj in reader:
        json_objects.append(obj)
        print(obj)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 3、自定义json编解码器


from datetime import datetime
import json

"""
使用意义
   
    处理复杂对象：
    
        意义：默认的 JSON 编码器只能处理基本的数据类型（如字符串、数字、列表和字典），无法处理复杂的自定义对象。通过自定义编码器和解码器，可以将复杂对象转换为 JSON 格式，并且能够正确地将 JSON 数据恢复为原始对象。
        示例：例如，处理包含日期时间、自定义类实例的对象，能够使这些对象轻松地在不同系统之间传递和存储。
 
    数据持久化：
    
        意义：在需要将复杂对象保存到文件或数据库中，并在稍后恢复这些对象时，自定义编码器和解码器可以确保数据的完整性和一致性。
        示例：例如，保存用户配置、应用程序状态等，可以在应用程序重启或迁移时恢复原始状态。
   
    网络通信：
    
        意义：在客户端和服务器之间传输复杂对象时，需要将对象序列化为 JSON 格式。自定义编码器和解码器可以确保数据在传输过程中保持一致，并且能够正确地在接收端恢复为原始对象。
        示例：例如，RESTful API 通信、WebSocket 消息传递等，能够确保复杂业务数据在网络传输中的一致性和可靠性。
    
    日志记录：
    
        意义：记录复杂对象的状态和变化，便于调试和分析。通过自定义编码器和解码器，可以将复杂对象转换为可读的 JSON 格式，便于日志的存储和查看。
        示例：例如，记录用户操作、系统事件等，可以在出现问题时快速定位和解决问题。
    
    提高灵活性和扩展性：
    
        意义：自定义编码器和解码器提供了处理数据的灵活性，可以根据具体需求进行扩展和定制，适应不同的业务场景。
        示例：例如，可以为不同版本的数据添加兼容性处理，确保在升级或迁移时数据的平滑过渡。
   
    错误处理和数据验证：
    
        意义：在序列化和反序列化过程中，可以添加错误处理和数据验证逻辑，确保数据的有效性和完整性，避免因数据格式问题导致的程序崩溃。
        示例：例如，处理无效的日期时间格式、缺失的必需字段等，能够提高系统的健壮性和可靠性。

使用方法
1. 自定义 JSON 编码器和解码器的使用方法如下：
    1.1 定义自定义类：定义需要序列化和反序列化的自定义类。
    1.2 定义自定义编码器：继承 json.JSONEncoder 并重写 default 方法。
    1.3 定义自定义解码器：继承 json.JSONDecoder 并重写 object_hook 方法。
    1.4 序列化和反序列化：
        1.4.1 使用 json.dumps 并传入自定义编码器类来序列化对象。
        1.4.2 使用 json.loads 并传入自定义解码器类来反序列化 JSON 字符串。

使用场景
2. 自定义 JSON 编码器和解码器的使用场景包括但不限于：
    2.1 复杂对象的序列化和反序列化：
        2.1.1 当你有复杂的自定义对象时，默认的 JSON 编码器和解码器无法处理这些对象。
        2.1.2 例如：日期时间对象、自定义类实例等。
    2.2 数据持久化：
        2.2.1 需要将复杂对象保存到文件或数据库中，并在稍后恢复这些对象时。
        2.2.2 例如：保存用户配置、应用程序状态等。
    2.3 网络通信：
        2.3.1 在客户端和服务器之间传输复杂对象时，需要将对象序列化为 JSON 格式。
        2.3.2 例如：RESTful API 通信、WebSocket 消息传递等。
    2.4 日志记录：
        2.4.1 记录复杂对象的状态和变化，便于调试和分析。
        2.4.2 例如：记录用户操作、系统事件等。

使用频率
3. 自定义 JSON 编码器和解码器的使用频率取决于具体应用场景：
    3.1 频繁使用：
        3.1.1 在需要处理大量复杂对象的应用程序中，自定义编码器和解码器会被频繁使用。
        3.1.2 例如：数据科学、金融分析、游戏开发等领域。
    3.2 偶尔使用：
        3.2.1 在简单应用程序中，自定义编码器和解码器的使用频率可能较低。
        3.2.2 例如：简单的配置文件保存、一次性的对象序列化等。

注意事项
4. 在使用自定义 JSON 编码器和解码器时，需要注意以下几点：
    4.1 处理复杂对象：
        4.1.1 确保自定义编码器能够正确处理所有需要序列化的对象类型。
        4.1.2 在 default 方法中添加对所有可能类型的处理逻辑。
    4.2 处理反序列化：
        4.2.1 确保自定义解码器能够正确处理所有需要反序列化的 JSON 数据。
        4.2.2 在 object_hook 方法中添加对所有可能数据结构的处理逻辑。
    4.3 性能考虑：
        4.3.1 自定义编码器和解码器可能会增加序列化和反序列化的时间。
        4.3.2 在性能要求较高的场景中，注意优化编码器和解码器的实现。
    4.4 错误处理：
        4.4.1 在编码器和解码器中添加错误处理逻辑，确保在处理异常数据时不会导致程序崩溃。
        4.4.2 例如：处理无效的日期时间格式、缺失的必需字段等。
    4.5 版本兼容性：
        4.5.1 在不同版本的应用程序之间传输数据时，确保自定义编码器和解码器的实现是兼容的。
        4.5.2 例如：添加版本号字段，处理不同版本的数据结构。
"""

'''
假设我们有一个自定义类 CustomObject，我们需要将其序列化为 JSON 字符串，并且能够从 JSON 字符串反序列化回 CustomObject 实例。

1、首先，定义一个简单的自定义类 CustomObject，它包含两个属性：name 和 timestamp。
2、自定义JSON编码器
    
    思路：当 json.dumps 遇到不能序列化的对象时，会调用 default 方法。
    我们可以通过继承 json.JSONEncoder 并重写 default 方法来处理自定义对象。

3、自定义 JSON 解码器
    思路：当 json.loads 遇到字典时，会调用 object_hook 方法。
    我们可以通过继承 json.JSONDecoder 并重写 object_hook 方法来处理自定义对象的反序列化。   

4、测试自定义编码器和解码器
    json_str = json.dumps(obj, cls=CustomEncoder, indent=4)

        1、obj:
            作用：这是需要被序列化的 Python 对象。在示例中，obj 是一个 CustomObject 实例。
            示例：例如，obj 可以是一个字典、列表、字符串、数字、布尔值、None 或者自定义对象。
     
        2、cls=CustomEncoder:
        
            作用：cls 参数指定了一个自定义的 JSON 编码器类。默认情况下，json.dumps 使用的是 json.JSONEncoder 类，但通过指定 cls=CustomEncoder，可以使用自定义的编码器来处理特定类型的对象。
            示例：在你的示例中，CustomEncoder 是一个继承自 json.JSONEncoder 的类，通过重写 default 方法，能够处理 CustomObject 类的实例。
            这个方法会将 CustomObject 实例转换为一个 JSON 可序列化的字典。
       
        3、indent=4:
        
            作用：indent 参数用于指定 JSON 字符串的缩进级别。它的值是一个整数，表示每一级嵌套的缩进空格数。设置 indent=4 会使生成的 JSON 字符串具有漂亮的缩进格式，便于阅读。
            示例：如果 indent 设置为 4，每一级嵌套的 JSON 数据都会缩进 4 个空格。这对于调试和查看 JSON 数据非常有用，因为可以使得 JSON 结构更清晰。

'''


class CustomObject:
    '''自定义类'''

    def __init__(self, name, timestamp):
        self.name = name
        self.timestamp = timestamp

    def __repr__(self):
        return f"CustomObject(name={self.name}, timestamp={self.timestamp})"


# 自定义 JSON 编码器
class CustomEncoder(json.JSONEncoder):

    # 重写 default 方法来处理自定义对象
    def default(self, obj):
        # 检查对象是否是 CustomObject 的实例
        if isinstance(obj, CustomObject):
            # 将 CustomObject 转换为字典
            return {
                'name': obj.name,
                'timestamp': obj.timestamp.isoformat()  # 将 datetime 转换为 ISO 格式的字符串
            }
        # 如果对象不是 CustomObject，则使用默认的编码器
        return super().default(obj)


# 自定义 JSON 解码器
class CustomDecoder(json.JSONDecoder):

    def __init__(self, *args, **kwargs):
        # 初始化时，传入 object_hook 方法
        super().__init__(object_hook=self.object_hook, *args, **kwargs)

    # 重写 object_hook 方法来处理自定义对象的反序列化。
    def object_hook(self, dct):
        # 检查字典是否包含 CustomObject 的特定键
        if 'name' in dct and 'timestamp' in dct:
            # 将字典转换为 CustomObject
            return CustomObject(dct['name'], datetime.fromisoformat(dct['timestamp']))
        # 如果字典不是 CustomObject 的表示，则返回原字典
        return dct


# 创建一个 CustomObject 实例
obj = CustomObject('example', datetime.now())

# 使用自定义编码器将对象序列化为 JSON 字符串
json_str = json.dumps(obj, cls=CustomEncoder, indent=4)
print("使用自定义序列化器序列化 JSON:\n")
print(json_str)

# 使用自定义解码器将 JSON 字符串反序列化为对象
decoded_obj = json.loads(json_str, cls=CustomDecoder)
print("\n反序列化成 Object:\n")
print(decoded_obj)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 4、使用JSON格式化输出

'''为了生成更具可读性的 JSON 输出，可以使用 indent 参数'''

data = {'name': 'Alice', 'age': 30, 'city': 'New York'}
json_str = json.dumps(data, indent=4)
print(json_str)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 5、 排序 JSON 对象的键
'''使用 sort_keys 参数可以生成按键排序的 JSON 输出'''

json_str = json.dumps(data, sort_keys=True, indent=4)
print(json_str)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 6、 处理 NaN、Infinity 和 -Infinity

'''
在处理包含 NaN、Infinity 和 -Infinity 的数据时，Python 的 json 模块默认会将这些值转换为 null。
如果希望严格遵循 JSON 规范，可以使用 allow_nan 参数来控制这一行为。

【allow_nan】: 一个布尔值参数，指定是否允许 NaN、Infinity 和 -Infinity。默认值为 True。如果设置为 False，遇到这些值时会抛出 ValueError 异常。
【math.nan】 是 Python 中表示 "Not a Number" (NaN) 的一种方式。
    NaN 是一种特殊的浮点数值，用于表示未定义或不可表示的数值。
    在数学运算中，NaN 通常用于表示不合法或错误的操作结果，例如 0 除以 0 或无效的浮点数运算    
    
以下是一个示例代码，由于设置了 allow_nan=False，当遇到 NaN 值时，json.dumps 会抛出 ValueError 异常，输出结果为：
    
    Out of range float values are not JSON compliant

'''
import math

data = {'value': math.nan}
try:
    json_str = json.dumps(data, allow_nan=False)
except ValueError as e:
    print(e)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 7、 处理大文件

'''对于大文件，可以使用逐行处理的方法来避免内存问题'''


def read_large_json(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            yield json.loads(line)


for obj in read_large_json(bigjson_path):
    print(obj)

# todo 8、 使用 json.tool 进行命令行格式化

'''
Python 提供了一个内置工具 json.tool，可以在命令行中格式化 JSON 数据:

    echo '{"name": "Alice", "age": 30, "city": "New York"}' | python -m json.tool

输出结果：
    
    {
        "name": "Alice",
        "age": 30,
        "city": "New York"
    }
'''

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 二、pickle模块

"""

【说明一下】
    json是一种所有的语言都可以识别的数据结构。如果我们将一个字典或者序列化成了一个json存在文件里，那么java代码或者js代码也可以拿来用。
但是如果我们用pickle进行序列化，其他语言就不能读懂这是什么了～所以，如果你序列化的内容是列表或者字典，我们非常推荐你使用json模块，
但如果出于某种原因你不得不序列化其他的数据类型，而未来你还会用python对这个数据进行反序列化的话，那么就可以使用pickle。


Java、Scala 和 Python 的字节流序列化对比简单看一下

Java、Scala 和 Python 都支持字节流序列化，但它们的实现方式和特性有所不同。以下是对这三种语言在字节流序列化方面的详细对比，包括注意事项和使用场景。

1. 序列化机制：
   - Java：
     - Java 提供了内置的序列化机制，通过实现 `java.io.Serializable` 接口来标记一个类可以被序列化。
     - 使用 `ObjectOutputStream` 将对象写入字节流，使用 `ObjectInputStream` 从字节流读取对象。
     - 需要注意的是，序列化的对象必须保证类的定义在反序列化时存在，并且类的版本（通过 `serialVersionUID`）要匹配。
     - 注意事项：
       - 序列化类的所有成员变量必须是可序列化的。
       - 静态成员变量不会被序列化。
       - 使用 `transient` 关键字可以标记不需要序列化的成员变量。
       - 序列化和反序列化过程中可能会抛出 `IOException` 和 `ClassNotFoundException`，需要进行异常处理。
     - 使用场景：
       - 网络传输对象数据。
       - 持久化对象状态到文件或数据库。
       - 缓存对象数据。

  1)IO : 输入输出
          
          输入 : 别的地方的数据到我的程序中来
          输出 : 我程序中的数据到别的地方
    
                            字节流                 字符流
          输入流          InputStream             Reader
          输出流          OutputStream            Writer
    
      文件流 : FileXxx
      缓冲流 : BufferedXxx
 
  2）编码和解码的说明
      
         编码(encode) : 字符串 => 字节数组, 用于存储文件或网络传输
         解码(decode) : 字节数组 => 字符串, 用于程序去读文件或接收网络数据
              "abc我和你yyy"--utf-8
              61 62 63 e6 88 91 e5 92 8c e4 bd a0 79 79 79
              0110 0001

          e6 88 91 : 称为UTF8编码的字符, 可变长编码方式
          1110 0110 1000 1000 1001 0001 ,
          要数第一个有几个连续的1, 有3个,说明这个字符使用3字节编码, 1110只是长度信息,不是数据部分
          后面的每个字节都必须要以10为开头, 不是数据部分
          1110 0110 1000 1000 1001 0001
          0110   00 1000   01 0001
          0110 0010 0001 0001 => 0x6211

          这是GBK : 适用于本地化
          61 62 63 CE D2 BA CD C4 E3 79 79 79
      特别的：
          ①在程序中String永远是Unicode字符序列.
         ②如果解码方式和编码方式不一致, 一定会出乱码

  3）序列化 : 把GC区中的对象的数据写入输出流, 对象可以持久化或进行传输.
         把多个对象放入集合或数组, 也可以直接序列化.

          ① 输出时必须要关闭流或flush, 这样才能保证数据真的刷入硬盘.
          ② 输出时如果要以追加的方式写文件, new FileXXX("文件", true);
  
     示例代码：
    
     /**
     * 练习 : 写二进制文件,写入20个随机的100以内的整数(写成int型)
     * 手工阅读文件, 尝试解决第1个和最后一个随机是多少.
     * 再用程序读出这些数据,并打印
     */
     
    @Test//测试包注释
    public void exer2() {
        FileInputStream fileInputStream = null;
        BufferedInputStream bufferedInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            fileInputStream = new FileInputStream("二进制文件");
            bufferedInputStream = new BufferedInputStream(fileInputStream);
            objectInputStream = new ObjectInputStream(bufferedInputStream);
            for (int i = 0; i < 20; i++) {

                int num = objectInputStream.readInt();
                System.out.println(num);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }


   - Scala：
     - Scala 也可以使用 Java 的序列化机制，因为 Scala 运行在 JVM 上，并且可以直接使用 Java 的类库。
     - 通过实现 `java.io.Serializable` 接口来标记一个类可以被序列化。
     - 使用 `ObjectOutputStream` 和 `ObjectInputStream` 类进行序列化和反序列化。
     - 注意事项与 Java 类似。
     - 使用场景与 Java 类似。

     示例代码：
     ```scala
     import java.io._

     @SerialVersionUID(1L)
     class Person(val name: String, val age: Int) extends Serializable

     object SerializationDemo {
         def main(args: Array[String]): Unit = {
             val person = new Person("Alice", 30)
             val oos = new ObjectOutputStream(new FileOutputStream("person.ser"))
             oos.writeObject(person)
             oos.close()

             val ois = new ObjectInputStream(new FileInputStream("person.ser"))
             val deserializedPerson = ois.readObject().asInstanceOf[Person]
             ois.close()

             println(s"${deserializedPerson.name}, ${deserializedPerson.age}")
         }
     }
     ```

   - Python：
       - Python 提供了 `pickle` 模块用于序列化和反序列化对象。
         
         pickle模块是将Python所有的数据结构以及对象等转化成bytes类型，然后还可以反序列化还原回去。
    
         pickle模块是只能Python语言识别的序列化模块。如果把序列化模块比喻成全世界公认的一种交流语言，也就是标准的话，json就是像是英语，全世界（python，java，php，C，等等）都遵循这个标准。而pickle就是中文，只有中国人（python）作为第一交流语言。
    
         既然只是Python语言使用，那么它支持Python所有的数据类型包括后面我们要讲的实例化对象等，它能将这些所有的数据结构序列化成特殊的bytes，然后还可以反序列化还原。使用上与json几乎差不多，也是两对四个方法。
       
       - 使用 `pickle.dump` 方法将对象序列化并写入字节流，使用 `pickle.load` 方法从字节流读取对象。
           
             用于网络传输：dumps、loads
        
             用于文件写读：dump、load
     
     - 注意事项：
       - `pickle` 模块不安全，尤其是从不信任的来源加载数据时，可能会执行任意代码。
       - 序列化的对象必须是可序列化的，即其类定义必须存在于反序列化时的环境中。
       - 可以使用 `dill` 模块作为 `pickle` 的扩展，支持更多的 Python 对象类型。
    
     - 使用场景：
       - 快速保存和加载 Python 对象。
       - 网络传输 Python 对象。
       - 缓存 Python 对象数据。

     示例代码：
     ```python
     import pickle

     class Person:
         def __init__(self, name, age):
             self.name = name
             self.age = age

     person = Person("Alice", 30)

     # 序列化对象
     with open('person.pkl', 'wb') as file:
         pickle.dump(person, file)

     # 反序列化对象
     with open('person.pkl', 'rb') as file:
         deserialized_person = pickle.load(file)

     print(deserialized_person.name, deserialized_person.age)
     ```

2. 序列化库：
   - Java：
     - 除了内置的序列化机制，Java 还有其他序列化库，如 Kryo、Protobuf、Jackson 等。
     - 这些库通常提供更高的性能和更多的功能。
     - Kryo：高性能序列化库，适用于大数据和高性能应用。
     - Protobuf：Google 的 Protocol Buffers，跨语言、高性能，适用于网络通信和数据存储。
     - Jackson：JSON 序列化和反序列化库，适用于 web 应用和 RESTful 服务。

   - Scala：
     - Scala 也可以使用 Java 的序列化库，如 Kryo、Protobuf、Jackson 等。
     - 另外，Scala 还有一些专门为其设计的序列化库，如 Chill（基于 Kryo）和 ScalaPB（基于 Protobuf）。
     - Chill：Twitter 开发的基于 Kryo 的序列化库，适用于大数据处理和分布式计算。
     - ScalaPB：基于 Protobuf 的序列化库，适用于跨语言通信和数据存储。

   - Python：
     - 除了 `pickle` 模块，Python 还有其他序列化库，如 `json`、`marshal`、`dill`、`msgpack`、`protobuf` 等。
     - `json`：轻量级数据交换格式，适用于 web 应用和 RESTful 服务。
     - `marshal`：内置模块，比 `pickle` 更快，但支持的对象类型有限。
     - `dill`：扩展的 `pickle`，支持更多的 Python 对象类型。
     - `msgpack`：高效的二进制序列化格式，适用于网络通信和高性能应用。
     - `protobuf`：Google 的 Protocol Buffers，跨语言、高性能，适用于网络通信和数据存储。

3. 序列化性能：
   - Java：
     - Java 内置的序列化机制比较简单易用，但性能相对较低，序列化后的数据体积较大。
     - 使用第三方库（如 Kryo）可以显著提高性能和减少数据体积。
     - Kryo 提供了高效的序列化和反序列化，适用于高性能需求的应用。
     - Protobuf 提供了紧凑的二进制格式，适用于网络通信和存储。

   - Scala：
     - Scala 使用 Java 的内置序列化机制时，性能与 Java 相当。
     - 使用专门为 Scala 设计的序列化库（如 Chill）可以进一步提高性能，特别是在处理复杂数据结构时。
     - Chill 基于 Kryo，提供了高效的序列化和反序列化。
     - ScalaPB 基于 Protobuf，提供了紧凑的二进制格式。

   - Python：
     - Python 内置的 `pickle` 模块使用方便，但性能较低，序列化后的数据体积较大。
     - 使用第三方库（如 `msgpack`、`protobuf`）可以提高性能和减少数据体积。
     - `msgpack` 提供了高效的二进制序列化，适用于高性能需求的应用。
     - `protobuf` 提供了紧凑的二进制格式，适用于网络通信和存储。

4. 类型安全：
   - Java：
     - Java 的序列化机制在编译时不进行类型检查，可能会导致运行时错误。
     - 使用第三方库（如 Protobuf）可以提供更好的类型安全性。
     - Protobuf 提供了强类型定义，通过 `.proto` 文件定义数据结构，生成的代码具有类型安全性。

   - Scala：
     - Scala 语言本身提供了更强的类型系统，结合序列化库（如 ScalaPB）可以实现更高的类型安全性。
     - 使用 Scala 的 case class 和模式匹配特性，可以更方便地处理序列化和反序列化。
     - ScalaPB 提供了强类型定义，通过 `.proto` 文件定义数据结构，生成的代码具有类型安全性。

   - Python：
     - Python 是动态类型语言，序列化和反序列化时不进行类型检查，容易导致运行时错误。
     - 使用类型注解和第三方库（如 `pydantic`）可以提高类型安全性，但仍然不如静态类型语言。
     - Protobuf 提供了强类型定义，通过 `.proto` 文件定义数据结构，生成的代码具有类型安全性。

总结：
Java、Scala 和 Python 在字节流序列化方面各有特点。Java 和 Scala 都可以使用 JVM 的序列化机制和库，Scala 由于其更强的类型系统和语言特性，在某些情况下可以提供更高的类型安全性和代码简洁性。Python 提供了简单易用的 `pickle` 模块，但性能和类型安全性较低，可以通过使用第三方库来改善。选择哪种语言和序列化库，取决于具体的应用需求和开发环境。
"""

# todo 2.1、 文件读写 对象流


import pickle

# 设置相对路径

input_pickle_path = '../../../input/input_pickle.pkl'
output_pickle_path = '../../../output/out_pickle.pkl'
my_pickle_path_1 = '../../../output/my_out_pickle_1.pkl'
my_pickle_path_2 = '../../../output/my_out_pickle_2.pkl'

# 创建一个示例对象
data = {'name': 'Alice', 'age': 25, 'is_student': True}

# ① 将对象序列化并写入文件 使用 pickle.dump 方法将对象序列化并写入文件：
with open(output_pickle_path, 'wb') as file:
    pickle.dump(data, file)

# ② 反序列化对象 使用 pickle.load 方法从文件中读取字节流并反序列化为对象：

# 从文件中读取并反序列化对象
with open(output_pickle_path, 'rb') as file:
    loaded_data = pickle.load(file)
    print(loaded_data)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 2.2、 网络传输 字节流

# ① 序列化为字节流 使用 pickle.dumps 方法将对象序列化为字节流
serialized_data = pickle.dumps(data)

# ② 反序列化字节流 使用 pickle.loads 方法将字节流反序列化为对象

# 将字节流反序列化为对象
loaded_data = pickle.loads(serialized_data)

print(loaded_data)

# todo 3、pickle序列化存储多个数据到一个文件中

dic1 = {'name': 'zhangguorong'}
dic2 = {'name': 'linqingxia'}
dic3 = {'name': 'wagnzuxian'}

#  使用with
with open(input_pickle_path, mode='wb') as f:
    pickle.dump(dic1, f)
    pickle.dump(dic2, f)
    pickle.dump(dic3, f)

#  使用文件句柄
f = open(input_pickle_path, mode='rb')
while True:
    try:
        print(pickle.load(f))
    except EOFError:
        break
f.close()

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 4、自定义序列化

'''
思路
    自定义序列化：
    
        使用 __reduce__ 方法：该方法返回一个元组，描述如何重建对象。元组的第一个元素是一个可调用对象，第二个元素是调用该对象的参数。
        使用 __getstate__ 和 __setstate__ 方法：__getstate__ 方法返回对象的状态，该状态可以是任何可序列化的对象。__setstate__ 方法接受一个状态参数，并用该状态来恢复对象。
        
    实现步骤：
    
        定义一个类，并实现 __reduce__ 方法或 __getstate__ 和 __setstate__ 方法。
        创建对象实例并序列化到文件。
        从文件反序列化对象并验证其状态。


在 Python 中，__reduce__ 和 __getstate__/__setstate__ 方法都可以用于自定义对象的序列化和反序列化，但它们的用法和适用场景有所不同。

__reduce__ 方法
    __reduce__ 方法返回一个元组，该元组描述如何重建对象。这个元组通常包含以下几个元素：

        1. 一个可调用对象（通常是类的构造函数或其他可调用对象）。
        2. 一个用于调用该对象的参数元组。
        3. （可选）一个用于恢复对象状态的额外参数。
    
        __reduce__ 方法适用于需要完全控制对象序列化和反序列化过程的情况。它允许你指定如何重建对象，以及在重建对象后如何恢复其状态。

__getstate__ 和 __setstate__ 方法
    __getstate__ 和 __setstate__ 方法用于更细粒度地控制对象的序列化和反序列化过程：
    
        - __getstate__ 方法返回一个对象的状态，该状态可以是任何可序列化的对象（通常是一个字典）。这个状态将被 pickle 模块序列化。
        - __setstate__ 方法接受一个状态参数，并用该状态来恢复对象。

    这些方法适用于对象的状态可以用简单的可序列化数据结构（如字典）表示的情况。
    __getstate__ 和 __setstate__ 方法提供了一种更直观的方式来控制对象的状态保存和恢复。


区别总结

    1. 灵活性：
        - __reduce__ 方法提供了最大的灵活性，允许你完全控制对象的重建过程。
        - __getstate__ 和 __setstate__ 方法提供了一种简单的方式来控制对象的状态保存和恢复，适用于对象状态可以用简单数据结构表示的情况。
    
    2. 复杂性：
        - __reduce__ 方法通常更复杂，因为你需要指定如何重建对象以及如何恢复其状态。
        - __getstate__ 和 __setstate__ 方法相对简单，只需要处理对象的状态。
    
    3. 使用场景：
        - 使用 __reduce__ 方法的场景包括需要自定义复杂对象的序列化过程，或对象包含不可序列化的成员。
        - 使用 __getstate__ 和 __setstate__ 方法的场景包括对象的状态可以用简单数据结构表示，且不需要自定义重建过程。


'''


# todo 4.1 使用__reduce__方法


class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __reduce__(self):
        # 返回一个元组，包含类和初始化参数，用于重建对象
        return (self.__class__, (self.name, self.age))


# 创建对象
person = Person("Alice", 30)

# 序列化对象到文件
with open(my_pickle_path_1, 'wb') as file:
    pickle.dump(person, file)

# 从文件反序列化对象
with open(my_pickle_path_1, 'rb') as file:
    deserialized_person = pickle.load(file)

# 验证对象状态
print(deserialized_person.name, deserialized_person.age)

print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

# todo 4.2 使用__getstate__和__setstate__方法

'''
copy 方法：在 __getstate__ 方法中使用，用于创建对象状态的浅拷贝，确保序列化过程中使用的是对象状态的副本。
update 方法：在 __setstate__ 方法中使用，用于将传入的状态字典中的键值对更新到对象的属性字典中，从而恢复对象的状态。
'''


class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __getstate__(self):
        # 返回对象的状态，通常是一个字典
        state = self.__dict__.copy()
        return state

    def __setstate__(self, state):
        # 从状态恢复对象
        self.__dict__.update(state)


# 创建对象
person = Person("Alice", 30)

# 序列化对象到文件
with open(my_pickle_path_2, 'wb') as file:
    pickle.dump(person, file)

# 从文件反序列化对象
with open(my_pickle_path_2, 'rb') as file:
    deserialized_person = pickle.load(file)

# 验证对象状态
print(deserialized_person.name, deserialized_person.age)
